<?php

/* @(#) $Id: connection.php 4 2009-02-17 22:44:07Z mo@ilaven.net $ */

/* A DatabaseConnection encapsulates a connection to the DBMS; it may support
 * connecting to either a single or multiple databases (represented by instances
 * of a class supporting IDatabase, typically a descendant of Database),
 * depending on the capabilities of the DBMS. Many DBMSes support accessing
 * more than one database in a single connection by way of fully-qualifying
 * object names (e.g., "database"."table" or "database"."schema"."table").
 */


/* A DBODatabaseConnection descendant is only created through a call
 * to DBODatabaseEngine::connect(). This ensures that singleton keys
 * are created consistently.
 */

abstract class DBOConnection implements IPathfinderClass, IDatabaseConnection
{
	protected static $instances = array();
	protected $refCount = 1;

	protected $persistKey;
	protected $persistEngine;

	public $engine;
	public $engineName = null;
	public $config;
	public $slaveConfig;

	protected $databases = array();

	protected $databaseClass = null;
	protected $tableClass = null;
	
	protected $tables = array();
	protected $tableSubst = array();
	
	public $queryLogging = false;
	public $queryLog = array();
		
	/* As of PHP 5.3, the following can be used: */

/*	public static function alloc()
	{
		$args = func_get_args();
		$inst = static::createInstanceArgs(null, $args);
		return $inst;
	} */

	public static function createInstance($className)
	{
		$args = func_get_args();
		array_shift($args);
		/* This is a hack to avoid having to repeat the implementation of
		 * createInstance in every descendant class. If this proves to be
		 * a horrific performance hog, then descendants will have to 
		 * duplicate the code in self::createInstanceArgs() as appropriate.
		 */
		$inst = call_user_func(array($className, 'createInstanceArgs'), $className, $args);
		return $inst;
	}

	public static function createInstanceArgs($className, $args, $key = null, $engine = null, $holder = null)
	{
		if($className === null) $className == __CLASS__;
		if(!$holder && isset($args[0]) && is_object($args[0])) $holder = $args[0];
		if(isset(self::$instances[$key]))
		{
			$inst = self::$instances[$key];
		}
		else
		{
			$reflect = new ReflectionClass($className);
			array_unshift($args, $engine); /* PersistEngine */
			array_unshift($args, $key); /* PersistKey */
			$inst = $reflect->newInstanceArgs($args);
			self::$instances[$key] = $inst;
		}
		$inst->retain($holder);
		return $inst;
	}

	
	public function __construct($persistKey, $persistEngine, $engine, $masterIRI, $slaveIRI)
	{
		$this->persistKey = $persistKey;
		$this->persistEngine = $persistEngine;
		$this->engine = $engine;
		$this->config = parse_url($masterIRI);
		if(!empty($this->config['path']) && substr($this->config['path'], 0, 1) == '/')
		{
			$this->config['name'] = substr($this->config['path'], 1);
		}
		$params = array();
		if(!empty($this->config['query']))
		{
			parse_str($this->config['query'], $params);
		}
		$this->config = array_merge($this->config, $params);
		if(!empty($this->config['pass'])) $this->config['password'] = $this->config['pass'];
		if(empty($this->config['host'])) $this->config['host'] = 'localhost';
		if(empty($this->config['password'])) $this->config['password'] = null;
		if(empty($this->config['user'])) $this->config['user'] = null;
		$this->init();
	}
	
	public function retain($obj = null)
	{
		$this->refCount++;
		return $this;
	}

	public function release($obj = null)
	{
		$this->refCount--;
		if(!$this->refCount)
		{
			$this->dealloc();
		}
	}
	
	protected function init()
	{
	}
	
	protected function dealloc()
	{
		$kl = array_keys($this->databases);
		foreach($kl as $k)
		{
			$this->databases[$k] = $this->databases[$k]->release($this);
		}
		$this->databases = array();
		unset($this->engine);
		unset($this->config);
	}
	
	public function getDatabase($holder, $masterIRI, $slaveIRI = null)
	{
		if(!strlen($this->databaseClass))
		{
			throw new Exception('Cannot create a new connection; connectionClass is empty');
		}
		$masterKey = $this->getDatabaseKey($masterIRI);
		$slaveKey = $this->getDatabaseKey($slaveIRI);
		$key = $this->persistKey . '/' . $masterKey . ($slaveKey ? '|' . $slaveKey : null);
		
		$inst = call_user_func_array(array($this->databaseClass, 'createInstanceArgs'),
			array($this->databaseClass, array($this, $masterIRI, $slaveIRI),
			$key, null, $this));
		$inst->retain($holder);
		$inst->release();
		return $inst;
	}
	
	protected function getDatabaseKey($iri)
	{
		/* This default implementation assumes the database name is the 'path'
		 * part of the IRI. If connections aren't being shared, it doesn't
		 * really matter what this function returns, as there'll only ever
		 * be one database per connection. If the per-database connection
		 * string information isn't the path, then this method can
		 * be overridden as necessary.
		 */
		if($iri === null) return null;
		$path = parse_url($iri, PHP_URL_PATH);
		if(substr($path, 0, 1) == '/') $path = substr($path, 1);
		if(substr($path, -1) == '/') $path = substr($path, 0, -1);
		return $path;
	}
	
	public function _logQuery($sql, $depth = 2, $time = null, $uri = null, $tag = null, $params = null)
	{
		if(!$this->queryLogging) return;
//		$loc = Eregansu::getBacktrace($depth + 1);
		$this->queryLog[] = array('sql' => $sql, 'loc' => null, 'time' => $time, 'uri' => $uri, 'tag' => $tag, 'params' => $params);
	}
	
	public function getTable($db, $name, $createMode)
	{
		return new $this->tableClass($db, $this, $name, $createMode);
	}
	
	public function registerTable($ident, $name, $db = null)
	{
		$this->tables[$ident] = array($name, $db);
		$this->tableSubst['[' . $ident . ']'] = $name;
	}
	
	public function getTableName($ident)
	{
		if(isset($this->tables[$ident]))
		{
			return $this->tables[$ident][0];
		}
		return null;
	}
	
	public function getTableDatabase($ident)
	{
		if(isset($this->tables[$ident]))
		{
			return $this->tables[$ident][1];
		}
		return null;
	}
	
	public function getQueryLog()
	{
		return $this->queryLog;
	}
	
	public /*INTERNAL*/ function _translateQuery($sql)
	{
		$sql = trim($sql);
		return str_replace(array_keys($this->tableSubst), array_values($this->tableSubst), $sql);
	}
	
	public function __sleep()
	{
		return array('config', 'slaveConfig', 'registry');
	}
	
	public function __wakeup()
	{
	}
}
